//
//  BBGestureDecoder.m
//  BBTUIOTest
//
//  Created by ben smith on 24/09/08.
//  Copyright 2008 __MyCompanyName__. All rights reserved.
//

#import "BBGestureDecoder.h"
#import "BBTUIOCursor.h"


@implementation BBGestureDecoder

@synthesize eventMask;
@synthesize vector;
@synthesize centroid;
@synthesize mouseEvent;
@synthesize scale;
@synthesize angle;
@synthesize delegate;

- (id) init
{
	self = [super init];
	if (self != nil) {
		newCursors = [[NSMutableArray alloc] init];
		oldCursors = [[NSMutableArray alloc] init];
	}
	return self;
}

-(void)addCursor:(BBTUIOCursor*)aCursor
{
	// couple things: this cursor might have hit me, but 
	// if it is not a new cursor, then it has been dragged into my bounds
	// and i should disregard it
	// if I already 'know' this cursor, then it is fine,
	// otherwise check it's freshness, if it is stale, then disregard
	BBTUIOCursor * match = [self matchingCursor:aCursor];
	if ((match == nil) && !aCursor.isNew) {
		return;
	}
	
	[newCursors addObject:aCursor];
}

-(BBTUIOCursor*)matchingCursor:(BBTUIOCursor*)newCursor
{
	for (BBTUIOCursor* cursor in oldCursors) {
		if ([cursor cursorID] == [newCursor cursorID]) {
			return cursor;
		}
	}
	return nil;
}

-(void)finishCursorCycle
{
	// figure out what the hell happened
	if (self.mouseEvent != nil) {
		// then it is a fake mouse event, just clear the event and get out
		self.mouseEvent = nil;
		return;
	}
	[self decodeGestures];
	/// now to clean out the cursor arrays
	[oldCursors removeAllObjects];
	[oldCursors addObjectsFromArray:newCursors];
	[newCursors removeAllObjects];
}

// we need to make a nice generic method to decode any gestures. 
// for now we really have two main types (for this layer)
// single touches:
//  drag/select
// double touches:
//  rotate/scale
-(void)decodeGestures
{
	eventMask = 0;
	scale = 0;
	angle = 0;
	if (([oldCursors count] == 0) && ([newCursors count] > 0)) {
		[self.delegate didStartManipulating];
	}
	if (([oldCursors count] > 0) && ([newCursors count] == 0)) {
		[self.delegate didEndManipulating];
	}
	vector = CGPointZero;
	if ([newCursors count] == 0) return;
	if ([newCursors count] == 1) [self handleSingleTouch];
	if ([newCursors count] == 2) [self handleDoubleTouch];
}

-(void)handleDoubleTouch
{
	// precondition: newCursors has two objects in it
	// first, simple gestures:
	// first check to see if we have matching cursors in the old list
	if ([oldCursors count] < 2) {
		// then the second hit is new, nothing to do yet
		return;
	}
	// 3 things can happen here
	// 1) a rotate event
	// 2) a scale event
	// 3) a drag event
	
	// ok, first see if we have two matching cursors
	BBTUIOCursor * new0 = [newCursors objectAtIndex:0];
	BBTUIOCursor * match0 = [self matchingCursor:new0];
	if (match0 == nil) return;
	BBTUIOCursor * new1 = [newCursors objectAtIndex:1];
	BBTUIOCursor * match1 = [self matchingCursor:new1];
	if (match1 == nil) return;

	// first deal with the drag
	// find the center of the old and new vectors
	// the diff is the drag
	float xcenterOld = (match1.screenPosition.x + match0.screenPosition.x) / 2.0;
	float ycenterOld = (match1.screenPosition.y + match0.screenPosition.y) / 2.0;

	float xcenterNew = (new1.screenPosition.x + new0.screenPosition.x) / 2.0;
	float ycenterNew = (new1.screenPosition.y + new0.screenPosition.y) / 2.0;

	//find the intersection of the two lines
	self.centroid = CGPointMake(xcenterOld,ycenterOld);
	
	self.eventMask += BBDragEventMask;
	vector.x = xcenterNew - xcenterOld;
	vector.y = ycenterNew - ycenterOld;
	
	// now find the angle
	
	float angleOld = [self angleOfPoint:match0.screenPosition around:match1.screenPosition];
	float angleNew = [self angleOfPoint:new0.screenPosition around:new1.screenPosition];
	
	self.eventMask += BBRotateEventMask;
	self.angle = angleNew - angleOld;
	
	// now the scale
	self.eventMask += BBScaleEventMask;
	float distOld = [self distanceFromPoint:match0.screenPosition to:match1.screenPosition];
	float distNew = [self distanceFromPoint:new0.screenPosition to:new1.screenPosition];
	
	self.scale = distNew/distOld;
}

-(float)distanceFromPoint:(NSPoint)p to:(NSPoint)center
{
	float ydiff = p.y - center.y;
	float xdiff = p.x - center.x;
	return hypotf(xdiff, ydiff);
}


-(float)angleOfPoint:(NSPoint)p around:(NSPoint)center
{
	float opposite = p.y - center.y;
	float adjacent = p.x - center.x;
	float radians = atanf(opposite/adjacent);
	float degrees = (radians * 57.2957795) + 90.0;
	if (adjacent < 0) degrees += 180;
	return degrees / 57.2957795;
}

-(void)handleSingleTouch
{
	// precondition: newCursors has one and only one object in it
	// first, simple gestures:
	
	// we have a touch, so we are selected
	eventMask += BBSelectEventMask;
	
	// if it is the first time I have seen any cursors, it is just a select
	if ([oldCursors count] == 0) return;
	
	// if there is only one, but I have seen it before
	// then it is a (possible) drag event
	// see if i have seen this new cursor before
	BBTUIOCursor * newCursor = [newCursors lastObject];
	BBTUIOCursor * matchingCursor = [self matchingCursor:newCursor];
	// if i havent seen it before, then it is a just select event, get out
	if (matchingCursor == nil) return;
	// now I have 2 matching cursors, it is a drag
	self.centroid = NSPointToCGPoint(matchingCursor.screenPosition);
	float xdiff = newCursor.screenPosition.x - matchingCursor.screenPosition.x;
	float ydiff = newCursor.screenPosition.y - matchingCursor.screenPosition.y;
	eventMask += BBDragEventMask;
	self.vector = CGPointMake(xdiff, ydiff);
}


-(void)addMouseDownEvent:(NSEvent*)theEvent
{
	eventMask = 0;
	angle = 0;
	scale = 0;
	vector = CGPointZero;
	eventMask += BBSelectEventMask;	
	self.mouseEvent = theEvent;
	mouseDown = YES;
}

-(void)addMouseDraggedEvent:(NSEvent*)theEvent
{
	if (!mouseDown) return;
	eventMask = 0;
	angle = 0;
	scale = 0;
	vector = CGPointMake([theEvent deltaX], -[theEvent deltaY]);
	eventMask += BBSelectEventMask;	
	eventMask += BBDragEventMask;	
	self.mouseEvent = theEvent;
}

-(void)addMouseUpEvent:(NSEvent*)theEvent
{
	mouseDown = NO;
}


@end
